<?php

namespace app\service\tool;

use yii\base\Model;

/**
 * 生成工具类
 * Class GenerateService
 * @package app\models
 */
class GenerateService extends Model
{

    /**
     * 获取：一个N位随机字符
     * @param int $len
     * @return string
     */
    public static function getRandomStr($len = 24)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $final = '';
        for ($i = 0; $i < $len; $i++) {

            $final .= $chars[mt_rand(0, strlen($chars) - 1)];
        }
        return $final;
    }

    /**
     * 获取：规定位数 规定区间的 随机小数
     * @param int $min
     * @param int $max
     * @param int $len
     * @return int
     */
    public static function getRandFloat($min = 0, $max = 1, $len = 2)
    {

        $len_ = 1;
        for ($i = 1; $i <= $len; $i++) {
            $len_ = $len_ . 0;
        }

        $len_ = intval($len_);

        $n = rand($min * $len_, $max * $len_) / $len_;

        return floor($n * $len_) / $len_;
    }

    /**
     * 获取：一个N位随机数字
     * @param int $len
     * @return string
     */
    public static function getRandInt($len = 24)
    {
        $chars = '0123456789';
        $final = '';
        for ($i = 0; $i < $len; $i++) {

            $final .= $chars[mt_rand(0, strlen($chars) - 1)];
        }
        return $final;
    }

    /**
     * 生成：新建MongoId
     * @return string
     */
    public static function newMongoId()
    {

        // mongo 扩展是否存在这个类 - 给 7.+ php
        if (class_exists('\MongoDB\BSON\ObjectID')) {

            return strval(new \MongoDB\BSON\ObjectID());
        }

        // mongo 扩展是否存在这个类 - 给 7.+ php
        if (class_exists('\MongoId')) {

            return strval(new \MongoId());
        }

        // 其他走PHP生成
        static $i = 0;
        $i or $i = mt_rand(1, 0x7FFFFF);
        return sprintf("%08x%06x%04x%06x",
            /* 4-byte value representing the seconds since the Unix epoch. */
            time() & 0xFFFFFFFF,

            /* 3-byte machine identifier.
             *
             * On windows, the max length is 256. Linux doesn't have a limit, but it
             * will fill in the first 256 chars of hostname even if the actual
             * hostname is longer.
             *
             * From the GNU manual:
             * gethostname stores the beginning of the host name in name even if the
             * host name won't entirely fit. For some purposes, a truncated host name
             * is good enough. If it is, you can ignore the error code.
             *
             * crc32 will be better than Times33.
            */
            crc32(substr((string)gethostname(), 0, 256)) >> 8 & 0xFFFFFF,

            /* 2-byte process id. */
            getmypid() & 0xFFFF,

            /* 3-byte counter, starting with a random value. */
            $i = $i > 0xFFFFFE ? 1 : $i + 1
        );
    }

    /**
     * 生成路径
     * @param $path string 渲染的路径
     * @param $basePath string 基础路径
     * @return mixed|string
     */
    public static function generatePath($path, $basePath = '')
    {

        // 路径组合
        $path = $basePath . $path;

        ### 渲染
        // 日期
        if (strstr($path, '{DATE}')) {
            $path = str_replace('{DATE}', date('Y-m-d'), $path);
        }
        // 年
        if (strstr($path, '{YEAR}')) {
            $path = str_replace('{YEAR}', date('Y'), $path);
        }
        // 月
        if (strstr($path, '{MONTH}')) {
            $path = str_replace('{MONTH}', date('m'), $path);
        }
        // 日
        if (strstr($path, '{DAY}')) {
            $path = str_replace('{DAY}', date('d'), $path);
        }
        // 随机字符串
        if (strstr($path, '{RANDOM}')) {
            $path = str_replace('{RANDOM}', self::getRandomStr(), $path);
        }

        return \Yii::getAlias($path);
    }

    /**
     * 是否为时间戳
     * @param $timestamp
     * @return bool
     */
    public static function isTimeStamp($timestamp)
    {

        return is_numeric($timestamp) &&
            strtotime(date('Y-m-d H:i:s', $timestamp)) === $timestamp;
    }

    /**
     * 数字转换为中文
     * @param string|integer|float $num 目标数字
     * @param integer|bool $mode 模式[true:金额（默认）,false:普通数字表示]
     * @param boolean $sim 使用小写（默认）
     * @return string
     */
    public static function numberToChinese($num, $mode = true, $sim = true)
    {
        if (!is_numeric($num)) {
            return '含有非数字非小数点字符！';
        }

        $char   = $sim ? array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九')
            : array('零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖');
        $unit   = $sim ? array('', '十', '百', '千', '', '万', '亿', '兆')
            : array('', '拾', '佰', '仟', '', '萬', '億', '兆');
        $retval = $mode ? '' : '点';
        //小数部分
        if (strpos($num, '.')) {
            list($num, $dec) = explode('.', $num);
            $dec = strval(round($dec, 2));
            if ($mode) {
                $retval .= "{$char[$dec[0]]}角{$char[$dec[1]]}分";
            } else {
                for ($i = 0, $c = strlen($dec); $i < $c; $i++) {
                    $retval .= $char[$dec[$i]];
                }
            }
        }
        //整数部分
        $str = $mode ? strrev(intval($num)) : strrev($num);
        for ($i = 0, $c = strlen($str); $i < $c; $i++) {
            $out[$i] = $char[$str[$i]];
            if ($mode) {
                $out[$i] .= $str[$i] != '0' ? $unit[$i % 4] : '';
                if ($i > 1 and $str[$i] + $str[$i - 1] == 0) {
                    $out[$i] = '';
                }
                if ($i % 4 == 0) {
                    $out[$i] .= $unit[4 + floor($i / 4)];
                }
            }
        }
        $retval = join('', array_reverse($out)) . $retval;
        return $retval;
    }

    /**
     * 获取时间周期
     * @param string $mode 模式
     *      day - 今天
     *      yesterday = 昨天,
     *      before_yesterday - 前天
     *      week - 本周
     *      last_week = 上周
     *      before_last_week - 上上周
     *      month - 本月
     *      last_month = 上月
     *      before_last_month - 上上月
     *      quarter - 本季度
     *      last_quarter= 上季度
     *      before_last_quarter - 上上季度
     *      year - 本年
     *      last_year = 去年
     *      before_last_year - 前年
     * @param int $dateTime
     * @return array|bool
     */
    public static function getTimeRange($mode = 'day', $dateTime = 0)
    {

        // 时间戳转换为时间文本
        if (!self::isTimeStamp($dateTime)) $dateTime = strtotime($dateTime);

        ### 昨天
        if ($mode == 'yesterday') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -1 days');
            $mode     = 'day'; // 模式改为[天]
        }
        ### 前天
        if ($mode == 'before_yesterday') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -2 days');
            $mode     = 'day'; // 模式改为[天]
        }

        ### 上周
        if ($mode == 'last_week') {
            $dateTime = $dateTime ?
                date('Y-m-d', $dateTime) : date('Y-m-d', strtotime('W'));
            $dateTime = strtotime($dateTime . ' -1 Weeks');
            $mode     = 'week'; // 模式改为[周]
        }
        ### 上上周
        if ($mode == 'before_last_week') {
            $dateTime = $dateTime ?
                date('Y-m-d', $dateTime) : date('Y-m-d', strtotime('W'));
            $dateTime = strtotime($dateTime . ' -2 week');
            $mode     = 'week'; // 模式改为[周]
        }

        ### 上季度
        if ($mode == 'last_quarter') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            // 初始化季度 减去一季度
            $quaNum   = ceil(date('n', strtotime($dateTime)) / 3);
            $dateTime = strtotime(
                date('Y-' . ($quaNum * 3)) . ' -' . (3 * 1) . ' month'
            );
            $mode     = 'quarter'; // 模式改为[季度]
        }
        ### 上上季度
        if ($mode == 'before_last_quarter') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            // 初始化季度 减去一季度
            $quaNum   = ceil(date('n', strtotime($dateTime)) / 3);
            $dateTime = strtotime(
                date('Y-' . ($quaNum * 3)) . ' -' . (3 * 2) . ' month'
            );
            $mode     = 'quarter'; // 模式改为[季度]
        }

        ### 去年
        if ($mode == 'last_year') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -1 year');
            $mode     = 'year'; // 模式改为[年]
        }
        ### 前年
        if ($mode == 'before_last_year') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -2 year');
            $mode     = 'year'; // 模式改为[年]
        }

        switch ($mode) {
            case 'week':
                ### 周
                $dateTime   = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
                $first      = 1; //表示每周星期一为开始日期 0表示每周日为开始日期
                $w          = date('w', strtotime($dateTime));
                $start_time = strtotime("$dateTime -" . ($w ? $w - $first : 6) . ' days');
                $end_time   = strtotime(date('Y-m-d', $start_time) . " + 6 days");
                break;
            case 'month':
                ### 月
                $dateTime   = $dateTime ? date('Y-m', $dateTime) : date('Y-m');
                $start_time = strtotime(date($dateTime . '-01 00:00:00'));
                $end_time   = strtotime('+1 month', $start_time) - 1;
                break;
            case 'quarter':
                ### 季度
                $dateTime = strtotime(
                    $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d')
                );
                // 年
                $year = date('Y', $dateTime);
                // 第几个季度
                $quaNum     = ceil(date('n', $dateTime) / 3);
                $quarter    = [1, 4, 7, 10];
                $quarter    = $quarter[$quaNum - 1];
                $start_time = strtotime(date(intval($year) . '-' . $quarter . '-01'));
                $end_time   = strtotime('+3 month', $start_time) - 1;
                break;
            case 'year':
                $dateTime   = $dateTime ? date('Y', $dateTime) : date('Y');
                $start_time = strtotime(date(intval($dateTime) . '-01-01'));
                $end_time   = strtotime('+1 year', $start_time) - 1;
                break;
            default:
                $dateTime   = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
                $start_time = strtotime($dateTime);
                $end_time   = strtotime('+1 day', $start_time) - 1;
                break;
        }
        return [
            'start_time' => $start_time, 'end_time' => $end_time
        ];
    }

    /**
     * 根据模式获取时间周期点
     * @param string $mode 模式
     *      day - 今天
     *      yesterday = 昨天,
     *      before_yesterday - 前天
     *      week - 本周
     *      last_week = 上周
     *      before_last_week - 上上周
     *      month - 本月
     *      last_month = 上月
     *      before_last_month - 上上月
     *      quarter - 本季度
     *      last_quarter= 上季度
     *      before_last_quarter - 上上季度
     *      year - 本年
     *      last_year = 去年
     *      before_last_year - 前年
     * @param int $dateTime
     * @return array|bool
     */
    public static function getTimeList($mode = 'day', $dateTime = 0)
    {

        // 时间戳转换为时间文本
        if (!self::isTimeStamp($dateTime) && !empty($dateTime)) {
            $dateTime = strtotime($dateTime);
        }

        ### 昨天
        if ($mode == 'yesterday') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -1 days');
            $mode     = 'day'; // 模式改为[天]
        }
        ### 前天
        if ($mode == 'before_yesterday') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -2 days');
            $mode     = 'day'; // 模式改为[天]
        }

        ### 上周
        if ($mode == 'last_week') {
            $dateTime = $dateTime ?
                date('Y-m-d', $dateTime) : date('Y-m-d', strtotime('W'));
            $dateTime = strtotime($dateTime . ' -1 Weeks');
            $mode     = 'week'; // 模式改为[周]
        }
        ### 上上周
        if ($mode == 'before_last_week') {
            $dateTime = $dateTime ?
                date('Y-m-d', $dateTime) : date('Y-m-d', strtotime('W'));
            $dateTime = strtotime($dateTime . ' -2 week');
            $mode     = 'week'; // 模式改为[周]
        }

        ### 上季度
        if ($mode == 'last_quarter') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            // 初始化季度 减去一季度
            $quaNum   = ceil(date('n', strtotime($dateTime)) / 3);
            $dateTime = strtotime(
                date('Y-' . ($quaNum * 3)) . ' -' . (3 * 1) . ' month'
            );
            $mode     = 'quarter'; // 模式改为[季度]
        }
        ### 上上季度
        if ($mode == 'before_last_quarter') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            // 初始化季度 减去一季度
            $quaNum   = ceil(date('n', strtotime($dateTime)) / 3);
            $dateTime = strtotime(
                date('Y-' . ($quaNum * 3)) . ' -' . (3 * 2) . ' month'
            );
            $mode     = 'quarter'; // 模式改为[季度]
        }

        ### 去年
        if ($mode == 'last_year') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -1 year');
            $mode     = 'year'; // 模式改为[年]
        }
        ### 前年
        if ($mode == 'before_last_year') {
            $dateTime = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
            $dateTime = strtotime($dateTime . ' -2 year');
            $mode     = 'year'; // 模式改为[年]
        }

        switch ($mode) {
            case 'week':
                ### 周
                $dateTime  = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
                $first     = 1; //表示每周星期一为开始日期 0表示每周日为开始日期
                $w         = date('w', strtotime($dateTime));
                $startTime = strtotime("$dateTime -" . ($w ? $w - $first : 6) . ' days');
                // $end_time   = strtotime(date('Y-m-d', $start_time) . " + 6 days");
                for ($i = 0; $i < 7; $i++) {
                    $dayTime      = strtotime(
                        date('Y-m-d', $startTime) . ' +' . $i . ' days'
                    );
                    $weekText     = $i == 6 ? '日' : self::numberToChinese($i + 1);
                    $timePoints[] = [
                        'text'       => '周' . $weekText,
                        'start_time' => strtotime(
                            date('Y-m-d 00:00:00', $dayTime)
                        ),
                        'end_time'   => strtotime(
                            date('Y-m-d 23:59:59', $dayTime)
                        ),
                    ];
                }
                break;
            case 'month':
                ### 月
                $dateTime   = $dateTime ? date('Y-m', $dateTime) : date('Y-m');
                $start_time = strtotime(date($dateTime . '-01 00:00:00'));
                $end_time   = date('d', strtotime('+1 month', $start_time) - 1);
                for ($i = 1; $i < $end_time; $i++) {
                    $timePoints[] = [
                        'text'       => $dateTime . '-' . $i,
                        'start_time' => strtotime($dateTime . '-' . $i),
                        'end_time'   => strtotime($dateTime . '-' . $i . ' 23:59:59')
                    ];
                }
                break;
            case 'quarter':
                ### 季度
                $dateTime = strtotime(
                    $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d')
                );
                // 年
                $year = date('Y', $dateTime);
                // 第几个季度
                $quaNum    = intval(ceil(date('n', $dateTime) / 3));
                $quarter   = [1, 4, 7, 10];
                $startYear = $quarter[$quaNum - 1];
                $endYear   = $quarter[$quaNum];
                for ($i = $startYear; $i < $endYear; $i++) {
                    $timePoints[] = [
                        'text'       => $year . '-' . $i,
                        'start_time' => strtotime(intval($year) . '-' . $i . '-01'),
                        'end_time'   => strtotime(
                                intval($year) . '-' . $i . '-01 +1 month'
                            ) - 1
                    ];
                }
                break;
            case 'year':
                $dateTime  = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
                $timestamp = strtotime($dateTime);
                for ($i = 1; $i <= 12; $i++) {
                    // 月份的最后一天不好计算 取下个月-1天
                    $startStamp   = strtotime(date('Y-' . $i . '-01 00:00:00', $timestamp));
                    $endTime      = strtotime('+1 month', $startStamp) - 1;
                    $timePoints[] = [
                        'text'       => date('Y-' . $i),
                        'start_time' => $startStamp,
                        'end_time'   => $endTime
                    ];
                }
                break;
            default:
                $dateTime  = $dateTime ? date('Y-m-d', $dateTime) : date('Y-m-d');
                $timestamp = strtotime($dateTime);
                for ($i = 0; $i < 24; $i++) {
                    $timePoints[] = [
                        'text'       => $i . ':00',
                        'start_time' => strtotime(date(
                            'Y-m-d ' . $i . ':00:00', $timestamp
                        )),
                        'end_time'   => strtotime(date(
                            'Y-m-d ' . $i . ':59:59', $timestamp
                        ))
                    ];
                }
                break;
        }

        return $timePoints;
    }

    /**
     * 根据区间获取时间周期点
     * @param string $mode 模式，day: 天
     * @param int $startTime
     * @param int $endTime
     * @param int $max 最大区间数，如：开始时间~结束时间, max=30, 取最多中间30天
     * @return array|bool
     */
    public static function getTimeListForRange($mode, $startTime, $endTime, $max = 30)
    {

        // 时间戳转换为时间文本
        if (self::isTimeStamp($startTime) && !empty($startTime)) {
            $startTime = date('Y-m-d H:i:s', $startTime);
        }
        if (self::isTimeStamp($endTime) && !empty($endTime)) {
            $endTime = date('Y-m-d H:i:s', $endTime);
        }
        $timePoints = [];

        switch ($mode) {
            default:
                $startTime = strtotime($startTime ? $startTime : date('Y-m-d'));
                $endTime   = strtotime($endTime ? $endTime : date('Y-m-d'));
                $days      = ($endTime - $startTime) / (60 * 60 * 24);
                for ($i = 0; $i <= $days; $i++) {
                    if ($max < ($i + 1)) break;
                    $day          = $i + 1;
                    $timePoints[] = [
                        'text'       => date('Y-m-' . $day, $startTime),
                        'start_time' => strtotime(date(
                            'Y-m-' . $day . ' 00:00:00', $startTime
                        )),
                        'end_time'   => strtotime(date(
                            'Y-m-' . $day . ' 23:59:59', $startTime
                        ))
                    ];
                }
                break;
        }

        return $timePoints;
    }

    /**
     * 计算某一年某个月有几周
     * @param $month
     * @return array
     */
    public static function getWeekNumInMonth($month)
    {
        $weekNum  = array(); //创建一个空数组
        $end_date = date('d', strtotime($month . ' +1 month -1 day')); //计算当前月有多少天

        for ($i = 1; $i < $end_date; $i = $i + 7) {
            //循环本月有多少周
            $w         = date('N', strtotime($month . '-' . $i)); //计算第一天是周几
            $weekNum[] = array(
                date('Y-m-d', strtotime(
                        $month . '-' . $i . ' -' . ($w - 1) . ' days')
                ), date(
                    'Y-m-d', strtotime($month . '-' . $i . ' +' . (7 - $w) . ' days')
                )
            );
        }
        return $weekNum;
    }
}
